/*********************************************************************/
/* dsPIC33E/PIC24E闪存程序存储器被分段为页和行，每页1024个指令字，每行128个指令字
 * dsPIC33E/PIC24E闪存程序存储器支持128个保持寄存器以一次对存储器的一行进行编程
 * dsPIC33E/PIC24E每个指令字为24位
 * dsPIC33E/PIC24E每个指令字占2个地址，例如0x000000和0x000001都是表示第一个指令字
 * dsPIC33E/PIC24E的存储空间大小为 4M*24位
 * TBLPAG：表地址页位，8位表地址页位与W寄存器组组合形成23位有效程序存储器地址加上一个字节选择位
 * DSRPAG：数据空间读页指针位，10位，DSRPAG复位时0x001，不能为0
 * dsPIC33E/PIC24E两个16位宽的地址空间并排放置
 * 组成32位空间，但是高8位不存在，高8位为虚拟字节
 * 对于大多数应用，24位中的高8位不用于存储数据，建议便成为NOP指令或者非法操作码值，TBLRDH和TBLWTH主要用于要求压缩数据存储的应用
 * 地址总是偶数呈现，读取奇数地址获取到的与前一个偶数时一致的，所以读取奇数地址没有意义
 * 每个地址数据由2个字合并而成，由以下四个寄存器操作
 * TBLRDL：表读低位字
 * TBLWTL：表写低位字
 * TBLRDH：表读高位字
 * TBLWTH：表写高位字
 * 
 * 程序存储器分为很多个表页，表页地址为TBLPAG，占据地址高8位（共24位）
 * 表页内部地址为16位的有效地址，占据低16位
 * 所以一个表页大小为0x010000
 * 表操作地址生成，有TBLPAG和来自Wn的16位组合成24位的有效地址
 * 7-->0  15------>0
 * TBLPAG+EA(有效地址)
 * 若是字节操作，则EA<0>为字节选择位，0操作低字节，1操作高字节
 * 
 * 读相关操作较为简单，只需要写入TBLPAB和TBLRDL或TBLRDH然后读取即可
 * 
 * 写相关操作较为复杂，允许一次擦除1页(8行)，运行一次编程1行
 * 注意擦除和编程都是边沿对齐的，从存储器起始开始，分别以1024指令字和128指令字作为边界
 * 即，擦除的地址单位长度为0x400，例如擦除0-0x0003ff，0x000400-0x0007ff；编程的地址单位长度为0x000080，例如，编程0-0x00007f，0x000080-0x0000ff
 * 在程序存储器的最后一页上执行页擦除操作会清零闪存配置字，从而使能代码保护。因此，用户应避免在程序存储器的最后一页上执行页擦除操作。
 * 要执行编程操作，必须先将其对应的整页(1024个指令字)全部擦除，然后重新编程，所以一般操作流程如下：
 * 1、读取闪存程序存储器的一页，并将其作为数据镜像存储到RAM中，RAM镜像必须从1024字程序存储器的偶地址边界读取
 * 2、用新的数据更新RAM中的数据镜像
 * 3、擦除闪存存储器页
 *  a)设置NVMCON寄存器以擦除一页
 *  b)禁止中断
 *  c)将要擦除页的地址也如NVMADRU和NVMADR寄存器（可以是该页任意地址）
 *  d)将秘钥序列吸入NVMKEY寄存器，以使能擦除
 *  e)将NVMCON<15>的WR位置1，启动擦除周期
 *  f)擦除周期结束时WR位清零
 *  g)允许中断
 * 4、用表写操作将128个指令字的一行从RAM装入写锁存器
 * 5、对一行进行编程
 *  a)设置NVMCON以编程一行
 *  b)禁止中断
 *  c)将要编程行的地址也如NVMADRU和NVMADR寄存器（可以是该行任意地址）
 *  d)将秘钥序列吸入NVMKEY寄存器，以使能编程周期
 *  e)将NVMCON<15>的WR位置1，启动编程周期
 *  f)擦除周期结束时WR位清零
 *  g)允许中断
 * 6、重复4-6步骤，对8行进行编程
 * 7、根据需要，重复1-6，对多页进行编程
 * 
 * 关联寄存器如下：
 * NVMCON：主控制寄存器，选择执行擦出还是编程操作
 * NVMKEY：只写寄存器，防止闪存被误写或者误擦除，编程或擦除前必须执行解锁序列，解锁期间禁止中断，①将0x55写入NVMKEY；②将0xAA写入NVMKEY；③执行2条NOP指令④一个周期中写入NVMCON寄存器
 * 
 */
/********* 没有Auxiliary存储的dsPIC33E/PIC24E的存储Map如下 ************/
/***************** ----     GOTO 指令       0x000000 *****************/
/***************** |        复位地址        0x000002 *****************/
/***************** |      中断向量表起始    0x000004 *****************/
/***************** |      中断向量表结束    0x0001FE *****************/
/*****用户存储空间  |       用户存储起始     0x000200 *****************/
/***************** |       用户存储结束     0x0xxxxx *****************/
/***************** |    Flash配置字节起始    0x0xxxxx *****************/
/***************** |    Flash配置字节结束    0x0xxxxx *****************/
/***************** |        未实现起始      0x0xxxxx *****************/
/***************** ----     未实现结束      0x7ffffe *****************/
/***************** ----     保留起始        0x800000 *****************/
/***************** |        保留结束        0x800FF6 *****************/
/***************** |       USERID起始       0x800FF8 *****************/
/***************** |       USERID结束       0x800FFE *****************/
/***************** |         保留起始       0x801000 *****************/
/***************** |         保留结束       0xF9FFFE  *****************/
/***************** |     写入锁存器起始      0xFA0000 *****************/
/*****系统存储空间  |     写入锁存器结束      0xFA0002 *****************/
/***************** |         保留起始       0xFA0004 *****************/
/****************  |         保留结束       0xFEFFFE *****************/
/***************** |        DEVID起始       0xFF0000 *****************/
/***************** |        DEVID结束       0xFF0002 *****************/
/***************** |         保留起始       0xFF0004 *****************/
/****************  ----      保留结束       0xFFFFFE *****************/
/********************************************************************/

//dsPIC33EP256GP50X, dsPIC33EP256MC20X/50X, PIC24EP256GP/MC20X这类256K的器件
//用户编程flash存储有88K个指令字空间，地址范围为:0x000200-0x02AFEA
//写锁存器占2个指令字，地址为：0xFA0000和0xFA0002
//USERID起始为0x800FF8，结束0x800FFE
//DEVID起始0xFF0000，结束0xFF0002

#include <xc.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

extern unsigned int MemReadHigh (unsigned int TablePage, unsigned int TableOffset);
extern unsigned int MemReadLow (unsigned int TablePage, unsigned int TableOffset);
extern unsigned int MemEraseOnePage (unsigned int TablePage, unsigned int TableOffset);
//写入2个指令字，即4个16bits，注意奇数位为高，仅8bits
//data[0]为第一个指令字的低16位，data[1]的低8位为第一个指令字的高8位，一个指令字宽度为32，最高8位为虚拟字节，所以实际宽度为24
extern unsigned int MemWriteDoubleInstructionWords (unsigned int TablePage, unsigned int TableOffset, unsigned int *data);


//每个指令字占2个地址，例如0x000000和0x000001都是表示第一个指令字
//flash分为多个表页，每一表页有0x10000/2个指令字，例如地址0x004000，为第0x00页，页内地址为:0x4000
//每个偶数地址包含2个16位数据，但实际上只有24位，高8位数据是虚拟的，此函数读取低八位
//可通过Mplab的PIC存储器视图->程序，查看hex文件对应地址的数据，然后使用下面方法读取测试比对
unsigned int InnerFlash_ReadFlashLow(unsigned int TablePage, unsigned int TableOffset)
{
    unsigned int Data1;
    //方法一
    //TBLPAG = TablePage;
    //Data1 = __builtin_tblrdl(TableOffset);
    
    //方法二
    Data1 = MemReadLow (TablePage, TableOffset);
    return Data1;
}

//每个指令字占2个地址，例如0x000000和0x000001都是表示第一个指令字
//flash分为多个表页，每一表页有0x10000/2个指令字，例如地址0x004000，为第0x00页，页内地址为:0x4000
//每个偶数地址包含2个16位数据，但实际上只有24位，高8位数据是虚拟的，此函数读取高八位
//可通过Mplab的PIC存储器视图->程序，查看hex文件对应地址的数据，然后使用下面方法读取测试比对
unsigned int InnerFlash_ReadFlashHigh(unsigned int TablePage, unsigned int TableOffset)
{
    unsigned int Data1;

    //方法一
    //TBLPAG = TablePage;
    //Data1 = __builtin_tblrdh(TableOffset);
    
    //方法二
    Data1 = MemReadHigh (TablePage, TableOffset);
    return Data1;
}

//每个指令字占2个地址，例如0x000000和0x000001都是表示第一个指令字
//flash分为多个表页，每一表页有0x10000/2个指令字，例如地址0x004000，为第0x00页，页内地址为:0x4000
//每个偶数地址包含2个16位数据，但实际上只有24位，高8位数据是虚拟的，此函数读取高八位
//可通过Mplab的PIC存储器视图->程序，查看hex文件对应地址的数据，然后使用下面方法读取测试比对
//擦除必须一次性擦除1024(实际地址偏移会*2，0x400*2，因为一组奇数和偶数表示一个指令字)个指令字，从0开始，边沿对齐，输入任意地址，会擦除包含这个地址在内的一页
unsigned int InnerFlash_EraseFlashPage(unsigned int TablePage, unsigned int TableOffset)
{
    MemEraseOnePage (TablePage, TableOffset);
    return 1;
}


//需要先擦除，再写入
//写入Flash，每个偶数地址仅写入低16位，高8位置0
//每个指令字的低16位存储一个数据，即2个地址存储1个16位数据
//例如 data[2]={0xaa05,0xaa06};，存入0x004000，即0x004000存入0x00aa05,0x004002存入0x00aa06
void InnerFlash_WriteFlashNoHigh(unsigned int TablePage, unsigned int TableOffset,unsigned int *data,unsigned int length)
{
    unsigned int dataTmp[4],i;
    for(i=0;i<length;i+=2)
    {
        //数组偶数位为指令字高16位(高8位为虚拟字节，仅低8位有效)，奇数位为指令字低16位
        //每个指令字存入一个实际16位数据，高8位为0
        dataTmp[0] = data[i];
        dataTmp[1] = 0x0000;
        dataTmp[2] = data[i+1];
        dataTmp[3] = 0x0000;
        //每次写入2个指令字，偏移4个地址，每2个地址表示一个指令字
        MemWriteDoubleInstructionWords(TablePage,TableOffset+2*i,dataTmp);
    }
    
}
